﻿/*
 Copyright (c) 2011.  Dirk Gordon

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Shell;
using System.Windows.Threading;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using System.IO;

namespace File_CRC32_Verifier_WPF
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Constants
        private const int CRC_BYTE_SIZE = 8;
        #endregion

        #region Objects
        private ObservableCollection<FileInformation> fileCollection = new ObservableCollection<FileInformation>();
        private CRC32 CRC32 = new CRC32();
        private DispatcherTimer taskbarUpdateTimer = new DispatcherTimer();
        #endregion

        /// <summary>
        /// Initialize.
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Setups the main window, CRC32, and timer.  Then checks for any files passed by command line.
        /// </summary>
        public void Start()
        {
            CRC32.Initialize();
            restoreWindowState();
            dataGrid.ItemsSource = fileCollection;

            //Tick every 1/10 of a second.
            taskbarUpdateTimer.Interval = new TimeSpan(0, 0, 0, 0, 100);
            taskbarUpdateTimer.Tick += new EventHandler(taskbarUpdateTimerTick);
            taskbarUpdateTimer.Start();

            //Hash commandline arguments removing the self reference.
            List<string> tmp = new List<string>(Environment.GetCommandLineArgs());
            if (tmp.Count > 1)
            {
                tmp.RemoveAt(0);
                hashList(tmp);
            }
        }

        /// <summary>
        /// Loads the application settings from a previously saved instance.
        /// </summary>
        private void restoreWindowState()
        {
            this.Top = Properties.Settings.Default.windowTop;
            this.Left = Properties.Settings.Default.windowLeft;
            this.Height = Properties.Settings.Default.windowHeight;
            this.Width = Properties.Settings.Default.windowWidth;
            this.WindowState = Properties.Settings.Default.windowState;
            dataGrid.Columns[0].Width = Properties.Settings.Default.column0width;
            dataGrid.Columns[1].Width = Properties.Settings.Default.column1width;
            dataGrid.Columns[2].Width = Properties.Settings.Default.column2width;
            dataGrid.Columns[3].Width = Properties.Settings.Default.column3width;
        }

        /// <summary>
        /// On the form closing event save the window state, so that the state can be reloaded later.
        /// Also, tell the CRC32 hash thread to exit.
        /// </summary>
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Properties.Settings.Default.windowTop = this.Top;
            Properties.Settings.Default.windowLeft = this.Left;
            Properties.Settings.Default.windowHeight = this.Height;
            Properties.Settings.Default.windowWidth = this.Width;
            Properties.Settings.Default.windowState = System.Windows.WindowState.Normal;
            Properties.Settings.Default.column0width = dataGrid.Columns[0].Width;
            Properties.Settings.Default.column1width = dataGrid.Columns[1].Width;
            Properties.Settings.Default.column2width = dataGrid.Columns[2].Width;
            Properties.Settings.Default.column3width = dataGrid.Columns[3].Width;

            Properties.Settings.Default.Save();

            //Send the hash thread the exit signal.
            CRC32.Exit();
        }

        /// <summary>
        /// Opens a file dialog and calls the hash function for each selected file.
        /// </summary>
        private void btnHash_Click(object sender, RoutedEventArgs e)
        {
            Microsoft.Win32.OpenFileDialog fileDialog = new Microsoft.Win32.OpenFileDialog();
            fileDialog.Multiselect = true;
            if (fileDialog.ShowDialog() == true)
            {
                hashList(fileDialog.FileNames);
            }
        }

        /// <summary>
        /// Opens the options window.
        /// </summary>
        private void btnOptions_Click(object sender, RoutedEventArgs e)
        {
            OptionsWindow optionsWindow = new OptionsWindow();
            optionsWindow.Owner = this;
            optionsWindow.ShowDialog();
        }

        /// <summary>
        /// Opens the CreateSFV window.
        /// </summary>
        private void btnCreateSFV_Click(object sender, RoutedEventArgs e)
        {
            CreateSFVWindow SFVWindow = new CreateSFVWindow(fileCollection);
            SFVWindow.Owner = this;
            SFVWindow.ShowDialog();
        }

        /// <summary>
        /// Handles drag and drop to the dataGrid.
        /// Hashes all files and directories that are dropped on the dataGrid.
        /// </summary>
        private void dataGrid_Drop(object sender, DragEventArgs e)
        {
            e.Handled = true;

            if (e.Data.GetDataPresent(DataFormats.FileDrop, false) == true)
            {
                e.Effects = DragDropEffects.All;
                hashList((string[])e.Data.GetData(DataFormats.FileDrop));
            }
        }

        /// <summary>
        /// The hash function will get the expected CRC from the filename and then enqueue the file to be hashed.
        /// </summary>
        /// <param name="file">The file to be hashed's full path.</param>
        public void hash(string file)
        {
            //Check if the file is a SFV file.
            if (System.IO.Path.GetExtension(file) == ".sfv")
            {
                processSFV(file);
            }
            else
            {
                FileInformation selectedFile = new FileInformation(file);

                //The delimiters that any expected CRC could appear in.
                char[] delimiters = new char[] { '{', '}', '(', ')', '[', ']' };
                string[] sections = System.IO.Path.GetFileName(selectedFile.fileName).Split(delimiters, StringSplitOptions.RemoveEmptyEntries);

                //Check each section to see if any of the sections are a CRC32 value.  The last possible value will be used.
                for (int i = 0; i < sections.Length; i++)
                {
                    //Regex to only get hexadecimal values.
                    if (sections[i].Length == CRC_BYTE_SIZE && Regex.IsMatch(sections[i], @"\A\b[0-9a-fA-F]+\b\Z"))
                    {
                        selectedFile.expectedCRC = sections[i].ToUpper();
                    }
                }

                fileCollection.Add(selectedFile);

                CRC32.Enqueue(selectedFile);
            }
        }

        /// <summary>
        /// The hashList function will get the expected CRC from the files and then call the hash function for each file.
        /// </summary>
        /// <param name="files">The files or directories to be hashed's full path.</param>
        public void hashList(IList<string> files)
        {
            for (int i = 0; i < files.Count; i++)
            {
                if (File.Exists(files[i]) || Directory.Exists(files[i]))
                {
                    //Argument is a directory.
                    if ((File.GetAttributes(files[i]) & FileAttributes.Directory) == FileAttributes.Directory)
                    {
                        string[] directoryFiles = Directory.GetFiles(files[i]);
                        foreach (string fileName in directoryFiles)
                        {
                            hash(fileName);
                        }
                    }
                    //Argument is a file.
                    else
                    {
                        hash(files[i]);
                    }
                }
            }
        }

        /// <summary>
        /// processSFV will open a SFV and read all the specified files and their expected CRC's.
        /// Then, it will call hashSFV for each file in the SFV.
        /// </summary>
        /// <param name="SFVFile"></param>
        public void processSFV(string SFVFile)
        {
            try
            {
                using (StreamReader streamReader = new StreamReader(SFVFile))
                {
                    string line, file, CRC;

                    while ((line = streamReader.ReadLine()) != null)
                    {
                        //Check to make sure the line isn't a comment
                        if (line[0] != ';')
                        {
                            bool foundCRC = false;

                            int i = line.Length - 1;
                            //9 is the minimum valid line size.
                            while (!foundCRC && i > 9)
                            {
                                //Ignore any spaces on the end.
                                if (line[i] != ' ')
                                {
                                    //Subtract 7 to get to the CRC start position.
                                    i -= 7;
                                    CRC = line.Substring(i, 8).ToUpper();
                                    foundCRC = true;

                                    file = System.IO.Path.GetDirectoryName(SFVFile) + "\\" + line.Substring(0, i).Trim();
                                    hashSFV(file, CRC);
                                }
                                else
                                {
                                    i--;
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                MessageBox.Show(e.ToString());
            }
        }

        /// <summary>
        /// hashSFV will take a filename and expected CRC and add them to the CRC32 hash queue.
        /// </summary>
        /// <param name="file">The full path of the file to hash.</param>
        /// <param name="expectedCRC">The expected CRC value of the file.</param>
        public void hashSFV(string file, string expectedCRC)
        {
            FileInformation selectedFile = new FileInformation(file);
            selectedFile.expectedCRC = expectedCRC;
            fileCollection.Add(selectedFile);
            CRC32.Enqueue(selectedFile);
        }

        /// <summary>
        /// This function removes the overflow button on the toolbar.
        /// </summary>
        private void ToolBar_Loaded(object sender, RoutedEventArgs e)
        {
            ToolBar toolBar = sender as ToolBar;
            var overflowGrid = toolBar.Template.FindName("OverflowGrid", toolBar) as FrameworkElement;
            if (overflowGrid != null)
            {
                overflowGrid.Visibility = Visibility.Collapsed;
            }
        }

        /// <summary>
        /// Every timer tick update the taskbar progress.
        /// </summary>
        private void taskbarUpdateTimerTick(object source, EventArgs e)
        {
            if (fileCollection.Count == 0)
            {
                TaskbarItemInfo.ProgressState = TaskbarItemProgressState.None;
            }
            else
            {
                double sum = 0;
                for (int i = 0; i < fileCollection.Count; i++)
                {
                    sum += fileCollection[i].progress;
                }

                TaskbarItemInfo.ProgressState = TaskbarItemProgressState.Normal;
                TaskbarItemInfo.ProgressValue = sum / ((double)fileCollection.Count * 100d);
            }
        }
    }
}
